#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <vector>
#include "block_queue.hpp"
#include "Log.hpp"
#include <atomic>

using namespace std;
using namespace wzx;//可以在全局和类成员函数内部展开命名空间, 而不能在类内部展开

#define DEFAULT_THREAD_NUMS 5

template<typename T>
class thread_pool
{
// using namespace wzx; --err
private:
    //成员函数也可以成为另一个类(thread)的回调函数
    void handle_task()
    {
        while(true)
        {
            if(_task_queue.empty() && _running_tag == false)//两个条件都是线程安全的
                break;

            //处理任务

            //version1: 
            //bug: 当_t_q里只剩最后一个任务时且此时线程池stop了, get_and_pop()之后, _t_q变为空, 线程会进入if里break, 导致最后一个任务无法被执行
            //T task = _task_queue.get_and_pop();
            // if(_task_queue.empty() && _running_tag == false) 
            //     break;

            //version2:
            atomic<bool> flag;//输出型参数: 来表示任务是否获得成功
            T task = _task_queue.get_and_pop(flag);//线程安全的
            if(flag == false && _running_tag == false)
                break;

            task();//可以并发执行
        }
    }

    void tag_run()//两个tag都是CAS的
    {
        _running_tag = true;
        _task_queue.tag_run();
    }

    void tag_stop()//too
    {
        _running_tag = false;
        _task_queue.tag_stop();
    }

    //构造函数不暴露给外部, 但也要有, 因为就算单例模式你也要创建一个对象
    thread_pool(int thread_nums = DEFAULT_THREAD_NUMS)
        :_thread_nums(thread_nums)
        ,_running_tag(false)
        ,_task_queue(false)//设置block_queue的tag
    {
        LOG(log_level::INFO) << " thread_pool construct success";
    }


public:
    static thread_pool<T>* get_singleton()//获取单例函数, 暴露给外部使用
    {
        if(_singleton_ptr == nullptr)//不用线程安全, 因为锁后还有一层判断, 但是单例生成完后多线程调用get_singleton()可以并发, 因为_singleton_ptr都不为空了
        {                            
            lock_guard<mutex> lmtx(_stc_mtx);//加锁保护后的缺点: 单例生成完了以后, 每次多线程访问get_singleton()时都需要串行, 效率低
            if(_singleton_ptr == nullptr)//当_singleton_ptr还未new时, 如果多线程同时访问get_singleton(), 在此行会有线程安全问题
            {
                _singleton_ptr = new thread_pool<T>;
                LOG(log_level::DEBUG) << "_singleton对象 construct success";
            }
        }

        return _singleton_ptr;
    }

    void start()
    {
        tag_run();//上下层都置true
        for(int i = 0; i < _thread_nums; ++i)
        {
            //直到线程池启动时, 才构建线程并绑定处理函数
            _threads.emplace_back(&thread_pool::handle_task, this);//1.成员函数传参必须要显示传地址[&]并且指名类域!!!, 2.传递this
            LOG(log_level::INFO) << _threads[i].get_id() << " start success";
        }
    }

    bool task_push(const T& task)
    {
        if(!_running_tag)//如果线程池已经stop了, 就不允许载入新任务了
            return false;

        _task_queue.push(task);
        return true;
    }


    //生产者调用(这里是主线程调用)
    void stop()
    {
        tag_stop();//上下层都置false
        //唤醒所有条件变量下的线程(消费者)!!![必要]
        _task_queue.consumer_cond_notify_all();

        LOG(log_level::INFO) << "thread_pool stop...";
    }

    void join_all()
    {
        for(auto& t : _threads)
        {
            LOG(log_level::INFO) << t.get_id() << "join";
            t.join();
        }
    }



private:
    int _thread_nums;
    vector<thread> _threads;
    block_queue<T> _task_queue;

    atomic<bool> _running_tag;//表示线程池是否正在运行

    //懒汉方式(延时加载)实现单例模式
    static inline thread_pool<T>* _singleton_ptr = nullptr;//静态成员在程序加载到内存中时就已创建好了, 而普通成员函数需要构造是才开辟空间
    static inline mutex _stc_mtx;//所以也需要静态的锁来保护

    // 1. 自定义类型的静态成员变量也可以使用inline关键字. 
    // 2.如果不显示给inline静态成员变量传初始值 它会自动初始化, 且规则和初始化任何其他普通变量完全一样
    
};



//静态成员变量: 声明和定义分离
//当在头文件中定义类并在同一头文件中初始化其静态成员变量时，如果该头文件被多个源文件包含，会导致[链接错误--重定义]。
//正确做法: (1).c++17之前: 在.h文件的类中声明静态变量, 在.cpp文件中定义

// template<typename T>
// thread_pool<T>* thread_pool<T>::_singleton_ptr = nullptr;

// template<typename T>
// mutex thread_pool<T>::_stc_mtx;


//(2).C++17及以后：使用 inline 变量。
//inline 关键字修饰变量时，允许其在多个翻译单元中被定义（即可以放在头文件中），链接器会确保它们最终合并为同一个定义。